// Fill out your copyright notice in the Description page of Project Settings.


#include "MidiPawn.h"
#include <vector>

AMidiPawn::AMidiPawn()
{
 	PrimaryActorTick.bCanEverTick = true;
	_midiIn = NULL; _xStart = -1; _yStart = -1;
	_xDiff = 0.0f; _yDiff = 0.0f; _zDiff = 0.0f;

	_box = CreateDefaultSubobject<UBoxComponent>(TEXT("PawnBox"));
	SetRootComponent(_box);
}

void AMidiPawn::BeginPlay()
{
	Super::BeginPlay();

	_midiIn = new RtMidiIn();
	if (!SelectDevice())
		UE_LOG(LogTemp, Warning, TEXT("Unable to find any Midi device"));
	_midiIn->setCallback(AMidiPawn::MidiCallback, this);
}

void AMidiPawn::EndPlay(const EEndPlayReason::Type reason)
{
	Super::EndPlay(reason);
	delete _midiIn;
}

void AMidiPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (_xDiff != 0.0f || _yDiff != 0.0f)
	{
		FVector vecX = GetActorRightVector() * _xDiff;
		FVector vecY = GetActorForwardVector() * _yDiff;
		SetActorLocation(vecX + vecY);
	}

	if (_zDiff != 0.0f)
		AddActorWorldOffset(FVector(0.0, 0.0, _zDiff));
}

void AMidiPawn::SetupPlayerInputComponent(UInputComponent* inputComponent)
{
	Super::SetupPlayerInputComponent(inputComponent);
	inputComponent->BindVectorAxis(EKeys::Mouse2D, this, &AMidiPawn::OnMouseMove);
}

void AMidiPawn::OnMouseMove(FVector v)
{
	AddControllerYawInput(v.X);
	AddControllerPitchInput(-v.Y);
}

void AMidiPawn::MidiCallback(double deltaT, std::vector<unsigned char>* msg, void* userData)
{
	if (msg && !msg->empty())
	{
		std::string cmd = std::to_string((*msg)[0]);
		for (size_t i = 1; i < msg->size(); ++i)
			cmd += "/" + std::to_string((*msg)[i]);
		UE_LOG(LogTemp, Warning, TEXT("MSG: %s"), UTF8_TO_TCHAR(cmd.c_str()));

		AMidiPawn* me = (AMidiPawn*)userData;
		switch ((*msg)[0])
		{
		case 176:  // 10110000 = Control Change
			me->ControlMotion((*msg)[1], (*msg)[2]); break;
		case 144:  // 10010000 = Note On
			me->NoteMotion((*msg)[1], (*msg)[2], true); break;
		case 128:  // 10000000 = Note Off
			me->NoteMotion((*msg)[1], (*msg)[2], false); break;
		}
	}
}

void AMidiPawn::ControlMotion(unsigned char num, unsigned char value)
{
	switch (num)
	{
	case 1:
		if (_xStart < 0) _xStart = value;
		else _xDiff = (float)(value - _xStart);
		break;
	case 2:
		if (_yStart < 0) _yStart = value;
		else _yDiff = (float)(value - _yStart);
		break;
	case 16:
		_xStart = -1; _yStart = -1;
		_xDiff = 0.0f; _yDiff = 0.0f; return;
	}
}

void AMidiPawn::NoteMotion(unsigned char num, unsigned char value, bool on)
{
	if (!on) { _zDiff = -0.0f; return; }
	switch (num)
	{
	case 36:  // G# / User
		_zDiff = -value * 0.1f;
		break;
	case 37:  // C / Chromatic
		_zDiff = value * 0.1f;
		break;
	}
}

bool AMidiPawn::SelectDevice()
{
	unsigned int numPorts = _midiIn->getPortCount();
	if (!numPorts) return false;

	for (unsigned int i = 0; i < numPorts; ++i)
	{
		std::string name = _midiIn->getPortName(i);
		_midiIn->openPort(i);
		UE_LOG(LogTemp, Warning, TEXT("Open Midi device: %s"), UTF8_TO_TCHAR(name.c_str()));
		return true;  // just open the first device
	}
	return false;
}
